#include <string>
#include <exception>
#include <stdexcept>
#include <stdlib.h>
#include <stdio.h>
#include <glload/gl_3_0.h>
#include <glload/gl_load.hpp>
#include <GL/freeglut.h>
#define WINDOW_TITLE_PREFIX "Chapter 2"

int CurrentWidth = 800,
	CurrentHeight = 600,
	WindowHandle = 0;


GLuint VertexShaderId, FragShaderId, ProgramId, VertexId, ColorId, VAOId, MatrixUnif;

const GLchar* VertexShader = 
{
	"#version 130\n"
	"in vec4 in_Position;\n"
	"in vec4 in_Color;\n"
	"out vec4 ex_Color;\n"
	"void main(void)\n"
	"{\n"
	"	gl_Position = in_Position;\n"
	"   ex_Color = in_Color;\n"
	"}\n"
};

const GLchar* FragShader =
{
	"#version 130\n"
	"in vec4 ex_Color;\n"
	"out vec4 out_Color;\n"
	"void main(void)\n"
	"{\n"
	"	out_Color = ex_Color;\n"
	"}\n"
};
		

unsigned FrameCount = 0;


void Initialize(int, char*[]);
void InitWindow(int, char*[]);
void ResizeFunction(int, int);
void RenderFunction(void);
void IdleFunction(void);
void TimerFunction(int);
void Cleanup(void);
void CreateVBO(void);
void DestroyVBO(void);
void CreateShader(void);
void DestroyShader(void);

int main(int argc, char* argv[])
{
	Initialize(argc, argv);
	glutMainLoop();
    return 0;
}

void Initialize(int argc, char* argv[])
{
	InitWindow(argc, argv);

	glload::LoadFunctions();
	CreateShader();
	CreateVBO();

	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
}

void InitWindow(int argc, char* argv[])
{
	glutInit(&argc, argv);
	
	glutInitContextVersion(3, 0);
	glutInitContextFlags(GLUT_FORWARD_COMPATIBLE);
	glutInitContextProfile(GLUT_CORE_PROFILE);

	glutSetOption(
		GLUT_ACTION_ON_WINDOW_CLOSE,
		GLUT_ACTION_GLUTMAINLOOP_RETURNS
	);
	
	glutInitWindowSize(CurrentWidth, CurrentHeight);

	glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);

	WindowHandle = glutCreateWindow(WINDOW_TITLE_PREFIX);

	if(WindowHandle < 1) {
		fprintf(
			stderr,
			"ERROR: Could not create a new rendering window.\n"
		);
		exit(EXIT_FAILURE);
	}

	glutReshapeFunc(ResizeFunction);
	glutDisplayFunc(RenderFunction);
	glutIdleFunc(IdleFunction);
	glutTimerFunc(0, TimerFunction, 0);
	glutCloseFunc(Cleanup);
}

void ResizeFunction(int Width, int Height)
{
	CurrentWidth = Width;
	CurrentHeight = Height;
	glViewport(0, 0, CurrentWidth, CurrentHeight);
}

void IdleFunction(void)
{
	glutPostRedisplay();
}

void TimerFunction(int Value)
{
	if (0 != Value) {
		char* TempString = (char*)
			malloc(512 + strlen(WINDOW_TITLE_PREFIX));

		sprintf(
			TempString,
			"%s: %d Frames Per Second @ %d x %d",
			WINDOW_TITLE_PREFIX,
			FrameCount * 4,
			CurrentWidth,
			CurrentHeight
		);

		glutSetWindowTitle(TempString);
		free(TempString);
	}
	
	FrameCount = 0;
	glutTimerFunc(250, TimerFunction, 1);

}

void RenderFunction(void)
{
	++FrameCount;


	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glDrawArrays(GL_TRIANGLES, 0, 3);

	glutSwapBuffers();
	glutPostRedisplay();
}

void Cleanup(void)
{
	DestroyShader();
	DestroyVBO();
}

void CreateVBO()
{
	GLfloat vertices[] = 
	{ 
		-0.8f, -0.8f,  0.0f,  1.0f, 
		 0.0f,  0.8f,  0.0f,  1.0f,
		 0.8f, -0.8f,  0.0f,  1.0f,
	};

	GLfloat colors[] =
	{
		 1.0f, 1.0f, 0.0f, 1.0f,
		 0.0f, 1.0f, 1.0f, 1.0f,
		 1.0f, 0.0f, 1.0f, 1.0f
	};



	GLenum result = glGetError();

	glGenVertexArrays(1, &VAOId);
	glBindVertexArray(VAOId);
	glUseProgram(ProgramId);

	glGenBuffers(1, &VertexId);
	glBindBuffer(GL_ARRAY_BUFFER, VertexId);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	glEnableVertexAttribArray(3);
	glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, 0, 0);

	glGenBuffers(1, &ColorId);
	glBindBuffer(GL_ARRAY_BUFFER, ColorId);
	glBufferData(GL_ARRAY_BUFFER, sizeof(colors), colors, GL_STATIC_DRAW);
	glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(1);
}

void CreateShader()
{
	GLenum result = glGetError();
	VertexShaderId = glCreateShader(GL_VERTEX_SHADER);
	
	glShaderSource(VertexShaderId, 1, &VertexShader, NULL);
	glCompileShader(VertexShaderId);

	FragShaderId = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(FragShaderId, 1, &FragShader, NULL);
	glCompileShader(FragShaderId);
	ProgramId = glCreateProgram();
	glAttachShader(ProgramId, VertexShaderId);
	glAttachShader(ProgramId, FragShaderId);
	glBindAttribLocation(ProgramId, 3, "in_Position");
	glBindAttribLocation(ProgramId, 1, "in_Color");

	glLinkProgram(ProgramId);
	glUseProgram(ProgramId);
}

void DestroyVBO()
{
	GLenum result = glGetError();

	glDisableVertexAttribArray(3);
	glDisableVertexAttribArray(0);

	glBindBuffer(GL_ARRAY_BUFFER, 0);

	glDeleteBuffers(1, &ColorId);
	glDeleteBuffers(1, &VertexId);
}

void DestroyShader()
{
	glUseProgram(0);

	glDetachShader(ProgramId, VertexShaderId);
	glDetachShader(ProgramId, FragShaderId);

	glDeleteShader(FragShaderId);
	glDeleteShader(VertexShaderId);
	glDeleteProgram(ProgramId);
}
